using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using EntLibContrib.PolicyInjection.Configuration;
using Microsoft.Practices.EnterpriseLibrary.PolicyInjection;
using EntLibContrib.PolicyInjection.RemotingInterception;
using System.Collections;

namespace EntLibContrib.PolicyInjection.CallHandlers
{
    /// <summary>
    /// An <see cref="ICallHandler"/> that synchronizes access to the next handler.
    /// </summary>
    [ConfigurationElementType(typeof(ThreadSafeCallHandlerData))]
    public class ThreadSafeCallHandler : ICallHandler
    {
        private static readonly object SyncRootProperty = new object();

        private string syncRootName;

        /// <summary>
        /// Creates a new <see cref="ThreadSafeCallHandler"/>.
        /// </summary>
        /// <param name="ThreadSafePolicyName">ThreadSafe.</param>
        public ThreadSafeCallHandler(string syncRootName)
        {
            this.syncRootName = syncRootName;
        }

        /// <summary>
        /// Gets the SyncRootName used by this handler.
        /// </summary>
        /// <value>SyncRootName.</value>
        public string SyncRootName
        {
            get { return syncRootName; }
        }

        #region ICallHandler Members

        /// <summary>
        /// Processes the method call.
        /// </summary>
        /// <remarks>This handler locks the access to the next handler based on the SyncRootName.</remarks>
        /// <param name="input"><see cref="IMethodInvocation"/> with information about the call.</param>
        /// <param name="getNext">delegate to call to get the next handler in the pipeline.</param>
        /// <returns>Return value from the target.</returns>
        public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
        {
            IMethodReturn result = null;

            object syncRoot = GetSyncRoot(input);

            lock (syncRoot)
            {
                result = getNext()(input, getNext);
            }

            return result;
        }

        #endregion

        protected virtual object GetSyncRoot(IMethodInvocation input)
        {
            IDictionary instanceContext = MethodInvocationExtensions.GetInstanceContext(input);

            if (instanceContext == null)
            {
                throw new ApplicationException(Resources.ContextualPolicyInjectorRequired);
            }

            Key syncRootKey = new Key(EqualityType.ReferenceEquals, SyncRootProperty, input.Target, SyncRootName);

            object syncRoot = null;

            lock (instanceContext.SyncRoot)
            {
                syncRoot = instanceContext[syncRootKey];

                if (syncRoot == null)
                {
                    syncRoot = new object();
                    instanceContext.Add(syncRootKey, syncRoot);
                }
            }

            return syncRoot;
        }
    }
}
